/**
 * @file Document.cpp
 */
#include "Document.h"
#include "DOMException.h"
#include "Node.h"
#include "../../common/CppMemPool.h"
#include "../../collections/tree/MultiChildTree.h"
#include "../sax/SAXException.h"
#include "../sax/SAXParser.h"

using namespace cppflib::io;
using namespace cppflib::collections::tree;
using namespace cppflib::xml::sax;

namespace cppflib
{

namespace xml
{

namespace dom
{

Document::Document(void)
{
   pTree = new MultiChildTree(false); // allow duplicated child keys
   cppmempool::Retain(pTree);
}

Document::~Document(void)
{
   cppmempool::Release(pTree);
}

/**
 *  Get the root node
 */
Node *Document::GetRootNode()
{
   return static_cast<Node*>(pTree->GetRootNode());
}

/**
 *  Get a node by a XML tag name
 *
 *  @param [in] tagName -- can be 
 *                             i) a single tag name or
 *                             ii) path to a nested tag node
 *                                 (e.g. "tag1/tag2/tag3/targetTag")
 *                         For both cases, name of root node is not required to be specified.
 *  @return the first matched node (if more than one) or null if not found
 */
Node *Document::GetNodeByTagName(pcwstr_t tagName)
{
   Node * pNode = static_cast<Node*>(pTree->GetRootNode());
   return pNode->GetChildNodeByTagName(tagName);
}

/**
 *  SAX Handler to parse content for a Document tree
 */
class DocumentSAXHandler : public SAXHandler
{
private:
    MultiChildTree *pTree;
    Node * pCurNode;

private:
    DocumentSAXHandler() { }

public:
    explicit DocumentSAXHandler(MultiChildTree *pTree)
    {
       this->pTree = pTree;
       cppmempool::Retain(this->pTree);
       pCurNode = NULL;
    }

    virtual ~DocumentSAXHandler()
    {
       cppmempool::Release(this->pTree);
    }

    virtual void StartDocument() 
    {}

    virtual void EndDocument()
    {}

    virtual void StartElement(const CFString &nameSpace, const CFString &fullTagName, 
                              const collections::LinkedHashtableT<CFString, CFString> &attributes)
    {
       Node *pNode = new Node(new CFString(fullTagName));
       pNode->SetAttributes(attributes);

       if (pTree->IsTreeEmpty())
          pTree->SetRootNode(pNode); // set as root node
       else
          pCurNode->AddChild(pNode); // add to child of current node

       pCurNode = pNode;
    }

    virtual void ElementContent(const CFString &text)
    {
       if (pCurNode)
          pCurNode->AppendTextContent(text);
    }

    virtual void EndElement(const CFString &nameSpace, const CFString &fullTagName)
    {
       if (pCurNode)
          pCurNode = static_cast<Node*>(pCurNode->GetParent()); // go back to parent
    }

    virtual void Error(const CFString &msg)
    {
       throw DOMException(msg);
    }

    virtual void FatalError(const CFString &msg)
    {
       throw DOMException(msg);
    }

    virtual void Warning(const CFString &msg)
    {  
    }
};

/**
 *  Get a XML Document by a file path. Internally, it makes use of SAXParser to do parsing.
 */
Document * Document::Parse(pcwstr_t xmlFilePath)
{   
   Document *pDoc = new Document();
   SAXParser parser;
   DocumentSAXHandler domHandler(pDoc->pTree);

   parser.Parse(xmlFilePath, domHandler);
   return pDoc;
}

/**
 *  Get a XML Document by a stream. Internally, it makes use of SAXParser to do parsing.
 */
Document * Document::Parse(Stream &stream)
{
   Document *pDoc = new Document();
   SAXParser parser;
   DocumentSAXHandler domHandler(pDoc->pTree);

   parser.Parse(stream, domHandler);
   return pDoc;
}

} // end of namespace dom

} // end of namespace xml

} // end of namespace cppflib

